#include <inc/lib.h>
#include <inc/elf.h>

#define UTEMP2USTACK(addr)    ((void*) (addr) + (USTACKTOP - PGSIZE) - UTEMP)
#define UTEMP2            (UTEMP + PGSIZE)
#define UTEMP3            (UTEMP2 + PGSIZE)

// Helper functions for spawn.
static int init_stack(envid_t child, const char **argv, uintptr_t *init_esp);

static int map_segment(envid_t child, uintptr_t va, size_t memsz,
                       int fd, size_t filesz, off_t fileoffset, int perm);

static int copy_shared_pages(envid_t child);
/*
 * Spawn就是创造一个子进程去运行目标文件程序
 */
// Spawn a child process from a program image loaded from the file system.
// prog: the pathname of the program to run.
// argv: pointer to null-terminated array of pointers to strings,
// 	 which will be passed to the child as its command-line arguments.
// Returns child envid on success, < 0 on failure.
int
spawn(const char *prog, const char **argv) {
    unsigned char elf_buf[512];
    struct Trapframe child_tf;
    envid_t child;

    int fd, i, r;
    struct Elf *elf;
    struct Proghdr *ph;
    int perm;

    // This code follows this procedure:
    //
    //   - Open the program file.打开程序文件
    //
    //   - Read the ELF header, as you have before, and sanity check its
    //     magic number.  (Check out your load_icode!)
    //     像之前一样，读取ELF的起始头，接着合理地去检查编号
    //
    //   - Use sys_exofork() to create a new environment.
    //     使用sys_exofork()函数来创建新的环境

    //   - Set child_tf to an initial struct Trapframe for the child.
    //     将child_tf设置为child的初始tf结构

    //   - Call the init_stack() function above to set up
    //     the initial stack page for the child environment.
    //     调用init_stack()函数来初始化子环境的栈页

    //   - Map all of the program's segments that are of p_type
    //     ELF_PROG_LOAD into the new environment's address space.
    //     Use the p_flags field in the Proghdr for each segment
    //     to determine how to map the segment:

    //     将p_type=ELF_PROG_LOAD的程序段全部映射到新环境的地址空间
    //     接着通过Proghdr.p_flags来决定每个段具体映射的实现方式

    //	* If the ELF flags do not include ELF_PROG_FLAG_WRITE,
    //	  then the segment contains text and read-only data.
    //	  Use read_map() to read the contents of this segment,
    //	  and map the pages it returns directly into the child
    //        so that multiple instances of the same program
    //	  will share the same copy of the program text.

    //        Be sure to map the program text read-only in the child.
    //        Read_map is like read but returns a pointer to the data in
    //        *blk rather than copying the data into another buffer.
    /*
     *    如果ELF flags中不存在 ELF_PROG_FLAG_WRITE，那么这个段存储的是只读数据和text段
     *    使用read_map()方法来读取这个段的内容 并将返回的页面直接映射到子环境中
     *    所以同一程序的多个实例都可以共享使用相同程序的text的同一副本
     *    (这样子达到的就是只读页面只用在内存造成一次开销，大大减少内存的开销)
     *
     *    确保在子环境中映射的是程序的只读数据段。
     *    Read_map与read类似，但返回一个指向*blk中数据的指针，而不是将数据复制到另一个缓冲区。
     */
    //	* If the ELF segment flags DO include ELF_PROG_FLAG_WRITE,
    //	  then the segment contains read/write data and bss.
    //	  As with load_icode() in Lab 3, such an ELF segment
    //	  occupies p_memsz bytes in memory, but only the FIRST
    //	  p_filesz bytes of the segment are actually loaded
    //	  from the executable file - you must clear the rest to zero.
    //        For each page to be mapped for a read/write segment,
    //        allocate a page in the parent temporarily at UTEMP,
    //        read() the appropriate portion of the file into that page
    //	  and/or use memset() to zero non-loaded portions.
    //	  (You can avoid calling memset(), if you like, if
    //	  page_alloc() returns zeroed pages already.)
    //        Then insert the page mapping into the child.
    //        Look at init_stack() for inspiration.
    //        Be sure you understand why you can't use read_map() here.
    //
    /*
     *    如果ELF flags中存在 ELF_PROG_FLAG_WRITE，则该段包含读/写数据和bss。
     *    与实验3中的load_icode()一样，这样的ELF段在内存中占据p_memsz字节，
     *    但是只有段的第一个p_filesz字节是真正从可执行文件中加载的--你必须将其余部分清零。
     *          对于每一个要被映射为读写段的页面，可以在UTEMP的父环境中暂时分配一个页面，
     *          将文件的适当部分读入该页面 并且/或 使用memset()将非加载部分清零。
     *          (如果你乐意 、如果page_alloc()返回的页面以及自动清零，那么你可以选择不使用memset())
     */
    //     Note: None of the segment addresses or lengths above
    //     are guaranteed to be page-aligned, so you must deal with
    //     these non-page-aligned values appropriately.
    //     The ELF linker does, however, guarantee that no two segments
    //     will overlap on the same page; and it guarantees that
    //     PGOFF(ph->p_offset) == PGOFF(ph->p_va).
    //
    /*
     *     注意：上面的段地址或长度都不能保证是页对齐的，所以你必须适当地处理这些非页对齐的值。
     *     然而，ELF链接器保证没有两个段在同一页面上重叠；
     *     并且保证PGOFF(ph->p_offset) == PGOFF(ph->p_va)
     *
     */
    //   - Call sys_env_set_trapframe(child, &child_tf) to set up the
    //     correct initial eip and esp values in the child.
    //
    //   - Start the child process running with sys_env_set_status().
    /*
     *   - 调用sys_env_set_trapframe(child, &child_tf)来设置子环境中正确的初始eip和esp值。
     *   - 用sys_env_set_status()启动运行中的子进程。
     */

    if ((r = open(prog, O_RDONLY)) < 0)
        return r;
    fd = r;

    // Read elf header
    elf = (struct Elf *) elf_buf;
    if (readn(fd, elf_buf, sizeof(elf_buf)) != sizeof(elf_buf)
        || elf->e_magic != ELF_MAGIC) {
        close(fd);
        cprintf("elf magic %08x want %08x\n", elf->e_magic, ELF_MAGIC);
        return -E_NOT_EXEC;
    }
    cprintf("666\n");

    // Create new child environment
    if ((r = sys_exofork()) < 0)
        return r;
    child = r;

    // Set up trap frame, including initial stack.
    child_tf = envs[ENVX(child)].env_tf;
    child_tf.tf_eip = elf->e_entry;

    if ((r = init_stack(child, argv, &child_tf.tf_esp)) < 0)
        return r;

    // Set up program segments as defined in ELF header.
    ph = (struct Proghdr *) (elf_buf + elf->e_phoff);
    for (i = 0; i < elf->e_phnum; i++, ph++) {
        if (ph->p_type != ELF_PROG_LOAD)
            continue;
        perm = PTE_P | PTE_U;
        if (ph->p_flags & ELF_PROG_FLAG_WRITE)
            perm |= PTE_W;
        if ((r = map_segment(child, ph->p_va, ph->p_memsz,
                             fd, ph->p_filesz, ph->p_offset, perm)) < 0)
            goto error;
    }
    close(fd);
    fd = -1;

    // Copy shared library state.
    if ((r = copy_shared_pages(child)) < 0)
        panic("copy_shared_pages: %e", r);

    child_tf.tf_eflags |= FL_IOPL_3;   // devious: see user/faultio.c
    if ((r = sys_env_set_trapframe(child, &child_tf)) < 0)
        panic("sys_env_set_trapframe: %e", r);

    if ((r = sys_env_set_status(child, ENV_RUNNABLE)) < 0)
        panic("sys_env_set_status: %e", r);
    return child;

    error:
    sys_env_destroy(child);
    close(fd);
    return r;
}

// Spawn, taking command-line arguments array directly on the stack.
// NOTE: Must have a sentinal of NULL at the end of the args
// (none of the args may be NULL).
int
spawnl(const char *prog, const char *arg0, ...) {
    // We calculate argc by advancing the args until we hit NULL.
    // The contract of the function guarantees that the last
    // argument will always be NULL, and that none of the other
    // arguments will be NULL.
    int argc = 0;
    va_list vl;
    va_start(vl, arg0);
    while (va_arg(vl, void *) != NULL)
        argc++;
    va_end(vl);

    // Now that we have the size of the args, do a second pass
    // and store the values in a VLA, which has the format of argv
    const char *argv[argc + 2];
    argv[0] = arg0;
    argv[argc + 1] = NULL;

    va_start(vl, arg0);
    unsigned i;
    for (i = 0; i < argc; i++)
        argv[i + 1] = va_arg(vl, const char *);
    va_end(vl);

    return spawn(prog, argv);
}


// Set up the initial stack page for the new child process with envid 'child'
// using the arguments array pointed to by 'argv',
// which is a null-terminated array of pointers to null-terminated strings.
//
// On success, returns 0 and sets *init_esp
// to the initial stack pointer with which the child should start.
// Returns < 0 on failure.
static int
init_stack(envid_t child, const char **argv, uintptr_t *init_esp) {
    size_t string_size;
    int argc, i, r;
    char *string_store;
    uintptr_t *argv_store;

    // Count the number of arguments (argc)
    // and the total amount of space needed for strings (string_size).
    string_size = 0;
    for (argc = 0; argv[argc] != 0; argc++)
        string_size += strlen(argv[argc]) + 1;

    // Determine where to place the strings and the argv array.
    // Set up pointers into the temporary page 'UTEMP'; we'll map a page
    // there later, then remap that page into the child environment
    // at (USTACKTOP - PGSIZE).
    // strings is the topmost thing on the stack.
    string_store = (char *) UTEMP + PGSIZE - string_size;
    // argv is below that.  There's one argument pointer per argument, plus
    // a null pointer.
    argv_store = (uintptr_t *) (ROUNDDOWN(string_store, 4) - 4 * (argc + 1));

    // Make sure that argv, strings, and the 2 words that hold 'argc'
    // and 'argv' themselves will all fit in a single stack page.
    if ((void *) (argv_store - 2) < (void *) UTEMP)
        return -E_NO_MEM;

    // Allocate the single stack page at UTEMP.
    if ((r = sys_page_alloc(0, (void *) UTEMP, PTE_P | PTE_U | PTE_W)) < 0)
        return r;


    //	* Initialize 'argv_store[i]' to point to argument string i,
    //	  for all 0 <= i < argc.
    //	  Also, copy the argument strings from 'argv' into the
    //	  newly-allocated stack page.
    //
    //	* Set 'argv_store[argc]' to 0 to null-terminate the args array.
    //
    //	* Push two more words onto the child's stack below 'args',
    //	  containing the argc and argv parameters to be passed
    //	  to the child's umain() function.
    //	  argv should be below argc on the stack.
    //	  (Again, argv should use an address valid in the child's
    //	  environment.)
    //
    //	* Set *init_esp to the initial stack pointer for the child,
    //	  (Again, use an address valid in the child's environment.)
    for (i = 0; i < argc; i++) {
        argv_store[i] = UTEMP2USTACK(string_store);
        strcpy(string_store, argv[i]);
        string_store += strlen(argv[i]) + 1;
    }
    argv_store[argc] = 0;
    assert(string_store == (char *) UTEMP + PGSIZE);

    argv_store[-1] = UTEMP2USTACK(argv_store);
    argv_store[-2] = argc;

    *init_esp = UTEMP2USTACK(&argv_store[-2]);

    // After completing the stack, map it into the child's address space
    // and unmap it from ours!
    if ((r = sys_page_map(0, UTEMP, child, (void *) (USTACKTOP - PGSIZE), PTE_P | PTE_U | PTE_W)) < 0)
        goto error;
    if ((r = sys_page_unmap(0, UTEMP)) < 0)
        goto error;

    return 0;

    error:
    sys_page_unmap(0, UTEMP);
    return r;
}

static int
map_segment(envid_t child, uintptr_t va, size_t memsz,
            int fd, size_t filesz, off_t fileoffset, int perm) {
    int i, r;
    void *blk;

    //cprintf("map_segment %x+%x\n", va, memsz);

    if ((i = PGOFF(va))) {
        va -= i;
        memsz += i;
        filesz += i;
        fileoffset -= i;
    }

    for (i = 0; i < memsz; i += PGSIZE) {
        if (i >= filesz) {
            // allocate a blank page
            if ((r = sys_page_alloc(child, (void *) (va + i), perm)) < 0)
                return r;
        } else {
            // from file
            if ((r = sys_page_alloc(0, UTEMP, PTE_P | PTE_U | PTE_W)) < 0)
                return r;
            if ((r = seek(fd, fileoffset + i)) < 0)
                return r;
            if ((r = readn(fd, UTEMP, MIN(PGSIZE, filesz - i))) < 0)
                return r;
            if ((r = sys_page_map(0, UTEMP, child, (void *) (va + i), perm)) < 0)
                panic("spawn: sys_page_map data: %e", r);
            sys_page_unmap(0, UTEMP);
        }
    }
    return 0;
}

// Copy the mappings for shared pages into the child address space.
static int
copy_shared_pages(envid_t child) {
    // LAB 5: Your code here.
    uintptr_t addr = 0;
    for (; addr < USTACKTOP; addr += PGSIZE) {
        if ((uvpd[PDX(addr)] & PTE_P) && (uvpt[PGNUM(addr)] & PTE_P)
        && (uvpt[PGNUM(addr)] & PTE_U) && (uvpt[PGNUM(addr)] & PTE_SHARE) ) {
            sys_page_map(sys_getenvid(), (void *) addr, child, (void *) addr, uvpt[PGNUM(addr)] & PTE_SYSCALL);
        }
    }
    return 0;
}

